#include "template.h" 
#include <sixemell.h>
#include "system.h"
#include <vector>
#include "winassert.h"
#include "tuxdocument.h"

namespace doc
{
	using std::string;
	using doc::Structure;
	using doc::Property;
	using doc::StructurePtrList;
	using std::vector;
	using sys::runtime_error_ex;
	using doc::TuxDocument;
	
	const char * TXT_STRUCTURE = "Structure";
	const char * TXT_ROOT_STRUCTURE = "RootStructure";
	const char * TXT_NAME = "Name";
	const char * TXT_YES = "yes";
	const char * TXT_NO = "no";
	const char * TXT_NONE = "None";
	const char * TXT_NULL = "(none)";
	const char * TXT_SECOND_NAME = "SecondName";
	
/* TODO (#1#): supply consts for other text - i.e. Hint etc ... */
	

	
 	Structure * FindByName(const string& name, const StructurePtrList &list)
 	{
 		StructurePtrList::const_iterator it = list.begin();
 		while (it!=list.end())
 		{
 			if ((*it)->Name() == name)
 				return *it;
 			it++;
 		}
 		return 0;
 	}
 	
 	Property * Structure::PropertyByName(const string& name) const
 	{
		const pointer_list<Property> &props = Properties();
		pointer_list<Property>::const_iterator pit = props.begin();
		Property * p;
		while (pit != props.end())
		{
			p = *pit;
			if (p->Name() == name)
				return p;
			pit++;
		}
		return 0;  // not found
	} 	

	const Structure& Structure::ChildByName(const string& name) const
	{
		Structure * pResult = FindByName(name,Childs());
		if (pResult == 0)
			throw runtime_error_ex("Child with name '%s' not found.",
								name.c_str());
    	return *pResult;
	}
	
	Property * Structure::SecondNameProperty() const
	{
		const string& sec = SecondName();
		if (sec != "")
		{
			Property *result = PropertyByName(sec);
			if (!result)
			{
	 			throw runtime_error_ex("Invalid second name value ('%s') "
     					"for structure '%s'",
     					sec.c_str(),
          				Name().c_str());		
			}
			return result;
		}
		else		
			return 0;
	}
	
 	string::size_type FindChar(string &s,vector<char> &list)
 	{
 		string::size_type r;
 		for (string::size_type i=0; i<list.size(); i++)
 		{
 			r = s.find(list[i]);
 			if (r != string::npos)
 				return r;
 		}
 		return string::npos;
 	}

	using sys::runtime_error_ex;
	using sixml::Node;
	using sixml::NodePtrList;
/* TODO (#1#): Rename eType to eNativeType */

	Property::eType Property::String2Type(const string& str)
	{
 		if (str == "TextProperty")
 			return pt_text;
 		else if (str == "NameProperty")
 			return pt_name;
 		else if (str == "BooleanProperty")
 			return pt_boolean;
 		else if (str == "ListProperty")
 			return pt_stringlist;
 		else if (str == "NodeListProperty")
 			return pt_nodelist;
 		else
 			throw runtime_error_ex("Invalid property type: '%s'",str.c_str());
	}

    Property * Property::Create(const string& name, const string& hint, eType type)
    {
        switch (type)
        {
            case pt_text:
                return new TextProperty(name, hint);
            case pt_name:
                return new NameProperty(name, hint);
            case pt_boolean:
            	return new BooleanProperty(name, hint);
            case pt_stringlist:
            	return new StringListProperty(name,hint);
            case pt_nodelist:
            	return new NodeListProperty(name,hint);
        }
		throw std::runtime_error("Could not create property");
    }

    Property * Property::Create(Node &n)
    {
    	eType type = Property::String2Type(n.Name());
    	string name = n.Attribute("Name");
    	string hint = n.Attribute("Hint");
    	Property * result = Property::Create(name,hint,type);
    	result->Load(n);
    	return result;
    }

	void Property::Load(Node &n)
    {
    	_default = n.Attribute("Default");
    }

    Property::Property(const string& name, const string& hint, Property::eType type)
    {
        _name = name;
        _hint = hint;
        _type = type;
    }
    
    TextProperty::TextProperty(const string& name, const string &hint)
    : Property(name,hint,pt_text)
    {
        
    }

    bool TextProperty::AcceptValue(string &s) const
    {
		return true;
    }


    NameProperty::NameProperty(const string& name, const string &hint)
    : Property(name,hint,pt_name)
    {
    
    }

    bool NameProperty::AcceptValue(string &s) const
    {
    	 string::size_type sPos = 0;
    	 string::size_type tPos = 0;
    	 const string::size_type len = s.size();
    	 string target(len,0);
    	 
    	 // copy alpha characters to target
    	 while (sPos < len)
    	 {
    	 	if (isalpha(s[sPos]) || (s[sPos] >= '0' && s[sPos] <= '9') )
    	 		target[tPos++] = s[sPos];
    	 	sPos++;
    	 };
		// copy back to source (if changed)
		if (tPos != sPos)
			s.assign(target,0,tPos+1);
		return true;
    }
    
    BooleanProperty::BooleanProperty(const string& name, const string &hint)
    : Property(name,hint,pt_boolean)
    {

    }

    bool BooleanProperty::AcceptValue(string &s) const
    {
    	if (s == TXT_YES || s == TXT_NO)
    		return true;
    	else
    		return false;
    }

    ListProperty::ListProperty(const string& name, const string &hint)
    : Property(name,hint,pt_list)
    {

    }

    bool ListProperty::AcceptValue(string &s) const
    {
   		return true;
    }

	bool ListProperty::IsReadOnly() const
	{
		return false;
		///return IsStrict() && Items(n).size() == 1;
	}


    StringListProperty::StringListProperty(const string& name, const string &hint)
    : ListProperty(name,hint)
    {

    }


	void StringListProperty::Load(Node &n)
    {
    	_isStrict = n.Attribute("Strict") == TXT_YES;
    	NodePtrList items;
    	n.SelectChildNodes("Item",items);
    	NodePtrList::iterator it = items.begin();
    	if (it == items.end())
    		throw runtime_error("ListProperty found without any items");
    	_default = (*items.begin())->Attribute(TXT_NAME);
    	while (it != items.end())
    	{
    		_items.push_back((*it)->Attribute(TXT_NAME));
    		it++;
    	}
    	
    }
	
    NodeListProperty::NodeListProperty(const string& name, const string &hint)
    : ListProperty(name,hint)
    {

    }

	bool NodeListProperty::IsReadOnly() const
	{
		return false;
	}

	void NodeListProperty::Load(Node &n)
    {
    	_path = n.Attribute("Path");
    	SetDefault(TXT_NULL);
    }
    
    const list<string> & NodeListProperty::Items(DocNode &current) const
    {
    	_items.clear();
    	_items.push_back(TXT_NULL);
    	DocNode * n = TuxDocument::Instance().GetRootNode();
    	ASSERT(n);
		int first_dollar = _path.find('$');
		if (first_dollar > -1)
		{			
			string path = _path;
			while (first_dollar > -1)
			{
				size_t second_dollar = path.find('$',first_dollar+1);
				if (second_dollar > 0)
				{
					string before = path.substr(0,first_dollar);
					string propname = path.substr(first_dollar+1,second_dollar-first_dollar-1);
					string after = path.substr(second_dollar+1);
					pointer_list<Attribute>::const_iterator it = current.Attribs().begin();
					while (it != current.Attribs().end())
					{
						if ((*it)->Type().Name() == propname)
						{
							path = before + (*it)->Value() + after;
							break;
						}
						it++;
					}
					first_dollar = path.find('$');
				}
				else
					first_dollar = -1;
			}
			n->AppendNodeNames(path.c_str(),_items);
		}
		else
    		n->AppendNodeNames(_path.c_str(),_items);
    	return _items;
    }
    
	Structure::eMultiplicity Structure::String2Multiplicity(const string& str)
 	{
 		if (str == "one-one")
 			return mp_11;
 		else if (str == "one-many")
 			return mp_1m;
 		else if ( str == "zero-one")
 			return mp_01;
 		else if (str == "zero-many")
 			return mp_0m;
 		else
 			throw runtime_error_ex("Invalid multiplicity value: '%s'",str.c_str());
 	}

	bool Structure::IsMandatory() const
	{
		const eMultiplicity m = GetMultiplicity();
		return (m == mp_11) || (m == mp_1m);
	}
	
	bool Structure::IsMany() const
	{
		const eMultiplicity m = GetMultiplicity();
		return (m == mp_0m) || (m == mp_1m);
	}
 	

	Template::Template()
	{
		_name = "";
	}

	void Template::Load(const string& fileName)
	{
		sixml::Node doc;
		doc.ReadFromFile(fileName);
		if (_pStructure)
			delete _pStructure;
		// Load linkables
        const sixml::Node& linkables 
        		= doc.SelectFirstChildNode("Linkables");
        NodePtrList::const_iterator lit = linkables.begin();
        _linkables.clear();
        while (lit != linkables.end())
        {
        	_linkables.push_back(new SimpleStructure(*(*lit),_linkables,true));
        	lit++;
        }

		// Load RootStructure
		_pStructure = 0;
		_pStructure = new SimpleStructure(doc.SelectFirstChildNode(TXT_ROOT_STRUCTURE),
  								_linkables,false);
		
		// extract template name and format it
		const char * start = fileName.c_str();
		const char * end;
		start += strlen(start);
		end = start;
		while (*start != '\\')
			start--;
		start++;
		while (*end != '.')
			end--;
		_name = start;
		_name.resize(end-start);
	}

	Template::~Template()
	{
	}
	
	bool Template::IsTemplate() const
	{
		return _name == "Template";
	}
	
    const Structure & Template::structure() const
    {
    	ASSERT(_pStructure);
     	return *_pStructure;
    }
    
	SimpleStructure::SimpleStructure(const Node &node,
 			const StructurePtrList & linkables, 
    		const bool Linkable)
	{
		/* pCurrentLinkable keeps the last linkable
		   created.  This is to allows recursion to be
     	   occuring more at a depth of more than one level */
		static SimpleStructure *pCurrentLinkable = 0;
		if (Linkable)
			pCurrentLinkable = this;
		// load attributes of structure
		_name = node.Attribute(TXT_NAME);
		_hint = node.Attribute("Hint");
  		const string& nameType = node.Attribute("NameType");
		_isNamed = nameType != TXT_NONE;
		_hasText = node.Attribute("HasText") == TXT_YES;
		_multiplicity = String2Multiplicity(node.Attribute("Multiplicity"));
		if (_isNamed)
		{
			Property::eType type = Property::String2Type(nameType);
			string hint = "The name of the ";
			hint += _name;
   			_pNameProperty = Property::Create(TXT_NAME,hint,type);
		}
		
		if (node.HasAttribute(TXT_SECOND_NAME))
		{
			_secondNameProperty = node.Attribute(TXT_SECOND_NAME);
		}
		else 
  			_secondNameProperty = "";
  			
		// load other properties specified
		_properties.clear();
		NodePtrList prop;
		node.SelectChildNodes("Properties", prop);
		if (prop.size() > 0)
		{
			const Node & propNode = *(*prop.begin());
			NodePtrList props;
			propNode.SelectChildNodes(props);
			NodePtrList::iterator prop = props.begin();
			while (prop != props.end())
			{
   				_properties.push_back(Property::Create(*(*prop)));
   				prop++;
			}
		}
		// Load substructures
		NodePtrList c;
  		node.SelectChildNodes(c);
		NodePtrList::const_iterator it = c.begin();
		while (it != c.end())
		{
			const Node& cn = *(*it);
			if (cn.Name() == "LinkStructure")
			{
	    		const string& linkName = cn.Attribute("LinkTarget");
	    		Structure * pLinkTarget;
	    		if (linkName == _name)
	    		{
	    			// recursive definition to parent
	    			pLinkTarget = this; 
    			}	
       			else
          		{
            		// link to already defined linkable?
       				pLinkTarget = FindByName(linkName,linkables);
       				if (pLinkTarget == 0)
       				{
       					// the "current" linkable?
       					if (pCurrentLinkable && pCurrentLinkable->Name() == linkName)
       						pLinkTarget = pCurrentLinkable;
       					else	
	       					throw runtime_error_ex(
    	        				"Link to undefined structure '%s' from structure '%s'.",
       		     				linkName.c_str(),_name.c_str());
            		}		
   				}

   				if (pLinkTarget->IsMandatory())
   					throw runtime_error_ex(
   							"Linkable structure '%s' must be optional.",
   							pLinkTarget->Name().c_str());
				_childs.push_back(new LinkedStructure(cn,*pLinkTarget));
			}
			else if (cn.Name() == "Structure")
				_childs.push_back(new SimpleStructure(cn,linkables,false));
			else
			{
				// ignore the node
			}
			it++;
		}
    }

	
	Structure::eMultiplicity SimpleStructure::GetMultiplicity() const
	{
		return _multiplicity;
	}	

	
	void Structure::GetMandatoryChilds(StructurePtrList &result) const
	{
		const StructurePtrList& childs = Childs();
  		StructurePtrList::const_iterator it = childs.begin();
  		while (it != childs.end())
  		{
     		if ((*it)->IsMandatory())
     			result.push_back(*it);
     		it++;
  		}
	}
	
	LinkedStructure::LinkedStructure(const Node &node,Structure & s)
	:  _linkTarget(s)
	{
	}
}

bool operator == (const doc::Structure &lhs, const doc::Structure& rhs)
{
	return (lhs.Name() == rhs.Name()) && 
 		   (lhs.Hint() == rhs.Hint());
}

